using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using Ares.PacketHandlers;
using System.Drawing;
using System.IO;

namespace cb0tServer
{
    partial class ServerCore
    {
        private void ProcessReceivedPacket(UserObject user, byte id, AresDataPacket packet)
        {
            byte temp_byte;
            ushort temp_int16;
            String name, text;
            CommandObject cmd;
            SharedFileObject fileobj;

            if (!user.logged_in && ((id > 2 && id < 112) || id > 112)) // only packet id 2 or 112 is acceptable to those not logged in
            {
                user.Disconnect();
                return;
            }

            if (id < 200)
            {
                if (packet.GetByteCount() > 4040)
                {
                    bool should_part = user.logged_in;

                    user.Disconnect();

                    if (should_part)
                        this.OnPart(user);

                    return;
                }
            }

            if (user.IsFlooding(id, packet.ToByteArray(), (uint)this.time_now))
            {
                user.Disconnect();
                this.OnPart(user);
                return;
            }

            switch (id) // process packet
            {
                case 2: // login
                    if (!user.logged_in)
                    {
                        user.guid = packet.ReadGuid();
                        user.files = packet.ReadInt16();
                        user.EncMode = packet.ReadByte() == 255 ? EncryptionMode.Encrypted : EncryptionMode.Unencrypted;

                        if (user.EncMode == EncryptionMode.Encrypted)
                        {
                            byte[] buf = packet.ToByteArray();
                            user.EncByte = (byte)buf.Length;
                            user.EncWord = 0;

                            for (int i = 0; i < buf.Length; i++)
                                user.EncWord += buf[i];
                        }

                        user.dc_port = packet.ReadInt16();
                        user.node_ip = packet.ReadIP();
                        user.node_port = packet.ReadInt16();
                        user.line = packet.ReadInt32();
                        user.name = packet.ReadString();
                        user.version = packet.ReadString();
                        user.local_ip = packet.ReadIP();
                        user.external_ip = packet.ReadIP();
                        temp_byte = packet.ReadByte();
                        user.browse = (temp_byte >= 3); // 3 = browse 4 = browse+compression old 7 = browse_compression new
                        user.supports_compression = (temp_byte > 3);
                        if (user.files == 0) user.browse = false;
                        user.custom_client = !(user.version.StartsWith("Ares 1.") || user.version.StartsWith("Ares 2."));
                        user.current_uploads = packet.ReadByte();
                        user.max_uploads = packet.ReadByte();
                        user.current_queued = packet.ReadByte();
                        user.orgName = user.name;
                        user.vroom = 0;
                        user.admin = false;
                        user.host = false;
                        user.kiddied = false;

                        if (packet.Remaining() > 3) // got personal information
                        {
                            user.user_age = packet.ReadByte();
                            user.user_sex = packet.ReadByte();
                            user.user_country = packet.ReadByte();
                            user.user_region = packet.ReadString();
                        }

                        if (this.OnJoinCheck(user)) // login authorised
                        {
                            Stats.UsersJoined++;
                            this.OnJoin(user);

                            if (CoreSettings.captcha)
                            {
                                if (user.level == 0 && !Captcha.IsTrusted(user.guid))
                                {
                                    user.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket(StringTemplate.GetString(153)));
                                    CaptchaObj cap = Captcha.Create();

                                    foreach (byte[] b in cap.packets)
                                        user.SendOutboundPacket(b);

                                    user.captcha_answer = cap.answer;
                                }
                                else user.has_captched = true;
                            }
                            else user.has_captched = true;
                        }
                        else // reject
                        {
                            user.Disconnect();

                            if (!user.auto_redirect)
                            {
                                Stats.UsersRejected++;
                                this.OnUserRejected(user);
                            }
                        }
                    }
                    else // login flood
                    {
                        this.OnFlood(user, "login flood");
                        this.OnPart(user);
                        Stats.UsersParted++;
                        Stats.FloodedUsers++;
                    }
                    break;

                case 4: // update
                    user.last_update_time = this.time_now;
                    temp_int16 = packet.ReadInt16();

                    if (user.files != temp_int16) UserPool.Broadcast(user.vroom, ServerOutboundPackets.UpdatePacket(user));

                    break;

                case 7: // auto password login
                    if (Passwords.IsAutoPasswordLogin(user, packet.ReadBytes()))
                        this.OnAdminStatusChanged(user);

                    break;

                case 8: // ping reply
                    if (packet.ReadString() == "0123456789")
                        UserPool.BroadcastPingReply(user, cb0tServer.Helpers.UnixTimeMS());

                    break;

                case 9: // avatar
                    user.avatar_count++;

                    if (user.avatar_count > 2)
                    {
                        user.avatar_count = 10;
                        return;
                    }

                    user.user_image = packet.ReadBytes();

                    try
                    {
                        Bitmap test_av = new Bitmap(new MemoryStream(user.user_image));
                    }
                    catch { user.user_image = null; }

                    UserPool.Broadcast(user.vroom, ServerOutboundPackets.AvatarPacket(user));

                    if (this.link_client.IsLinking())
                        this.link_client.SendOutboundPacket(ServerOutboundPackets.AvatarPacket(user));

                    break;

                case 10: // public text
                    text = packet.ReadString();
                    text = text.Replace("", "");

                    if (!user.has_captched)
                    {
                        String cap_str = text;
                        cap_str = Helpers.StripColors(cap_str);
                        cap_str = cap_str.Trim();

                        if (cap_str.Length > 0)
                        {
                            user.SendOutboundPacket(ServerOutboundPackets.PublicTextPacket(user.name, cap_str));
                            int cap;

                            if (int.TryParse(cap_str, out cap))
                            {
                                if (user.captcha_answer == cap)
                                {
                                    user.has_captched = true;
                                    user.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket(StringTemplate.GetString(154)));
                                    Captcha.MakeTrusted(user.guid);
                                    return;
                                }
                            }
                        }

                        user.captcha_attempts++;

                        if (user.captcha_attempts > 2)
                        {
                            user.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket(StringTemplate.GetString(151)));
                            user.Disconnect();
                            this.OnPart(user);
                            Stats.UsersParted++;
                            Stats.KickedUsers++;
                            return;
                        }
                        else
                        {
                            user.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket(StringTemplate.GetString(152)));
                            CaptchaObj cap = Captcha.Create();

                            foreach (byte[] b in cap.packets)
                                user.SendOutboundPacket(b);

                            user.captcha_answer = cap.answer;
                            return;
                        }
                    }

                    Stats.PublicMessagesReceived++;

                    if (text.StartsWith("#")) // command text
                    {
                        cmd = CommandEval.EvaluateClientCommandString(text.Substring(1));
                        this.OnCommand(user, cmd.cmdText, cmd.tUser, cmd.args);
                    }

                    if (user.IsSet()) text = this.OnTextBefore(user, text); // still connected? (invalid login attempt??)

                    if (user.IsSet()) // still connected? (word filter??)
                        if (!String.IsNullOrEmpty(text))
                            this.OnTextAfter(user, text);

                    break;

                case 11: // emote text
                    if (!user.has_captched)
                        return;

                    text = packet.ReadString();
                    text = text.Replace("", "");
                    Stats.EmoteMessagesReceived++;

                    text = this.OnEmoteBefore(user, text);

                    if (user.IsSet()) // still connected? (word filter??)
                        if (!String.IsNullOrEmpty(text))
                            this.OnEmoteAfter(user, text);

                    break;

                case 13: // personal message
                    text = packet.ReadString();
                    text = text.Replace("", "");

                    while (Encoding.UTF8.GetByteCount(text) > 50)
                        text = text.Substring(0, text.Length - 1);

                    if (PMessages.IsAdminMsg(user) != null)
                        return;

                    if (user.idle && !text.StartsWith("[idle] "))
                        text = "[idle] " + text;

                    user.user_message = text;
                    UserPool.Broadcast(user.vroom, ServerOutboundPackets.PersonalMessagePacket(user, text));

                    if (this.link_client.IsLinking())
                        this.link_client.SendOutboundPacket(ServerOutboundPackets.PersonalMessagePacket(user, text));

                    break;

                case 25: // pm text
                    if (!user.has_captched)
                        return;

                    name = packet.ReadString();
                    text = packet.ReadString();
                    text = text.Replace("", "");
                    Stats.PrivateMessagesReceived++;

                    if (name == CoreSettings.room_bot)
                    {
                        if (text.StartsWith("/") || text.StartsWith("#")) // command via host pm
                        {
                            text = text.Substring(1);
                            cmd = CommandEval.EvaluateClientCommandString(text);
                            this.OnCommand(user, cmd.cmdText, cmd.tUser, cmd.args);
                        }
                        else
                        {
                            this.OnHostPM(user, text);
                        }

                        return;
                    }

                    /*     if (name == this.last_pm_spam) // pm spammer
                         {
                             if (user.level < 5)
                             {
                                 this.banstats.Add(user.name + " [" + user.physical_ip + "] was banned for being a pm spammer");
                                 AdminLog.WriteLine(user.name + " was banned for being a pm spammer");
                                 Bans.AddBan(user);
                                 user.Disconnect();
                                 this.OnPart(user);
                                 Stats.UsersParted++;
                                 Stats.BannedUsers++;
                                 return;
                             }
                         }*/

                    this.OnPM(user, name, text);
                    break;

                case 45: // ignore request
                    temp_byte = packet.ReadByte();
                    name = packet.ReadString();
                    this.OnIgnoreRequested(user, name, temp_byte);
                    break;

                case 50: // add share
                    user.file_count++;

                    if (user.file_count > 12000) // too many files
                    {
                        this.OnFlood(user, "file share limit exceeded");
                        this.OnPart(user);
                        Stats.UsersParted++;
                        return;
                    }

                    fileobj = FileBrowseHelpers.CreateFileObject(packet);

                    if (fileobj != null)
                    {
                        user.AddFile(fileobj);
                        this.OnFileReceived(user, fileobj);
                    }

                    break;

                case 80:
                    byte[] _unzipped = null;

                    try
                    {
                        _unzipped = ZLib.Zlib.Decompress(packet.ReadBytes(), false);
                    }
                    catch { user.file_count++; }

                    if (_unzipped != null)
                    {
                        AresDataPacket _unzipped_files = new AresDataPacket(_unzipped);

                        while (_unzipped_files.Remaining() > 3)
                        {
                            temp_int16 = _unzipped_files.ReadInt16();

                            if (_unzipped_files.ReadByte() != 50)
                            {
                                user.file_count++;
                                break; // i'm only interested in add shares
                            }

                            byte[] _file_data = _unzipped_files.ReadBytes(temp_int16);

                            user.file_count++;

                            if (user.file_count > 12000) // too many files
                            {
                                this.OnFlood(user, "file share limit exceeded");
                                this.OnPart(user);
                                Stats.UsersParted++;
                                return;
                            }

                            fileobj = FileBrowseHelpers.CreateFileObject(new AresDataPacket(_file_data));

                            if (fileobj != null)
                            {
                                user.AddFile(fileobj);
                                this.OnFileReceived(user, fileobj);
                            }
                        }
                    }

                    break;

                case 51: // rem share
                    break;

                case 52: // browse request
                    FileBrowseHelpers.ProcessBrowseRequest(this.link_client, user, packet);
                    break;

                case 60: // room search request
                    FileBrowseHelpers.ProcessSearchRequest(user, packet);
                    break;

                case 64: // client uses proxy server
                    if (!user.admin)
                    {
                        this.tor.Update(user.physical_ip);
                        AdminLog.ReportProxy(user);
                        Bans.AddBan(user);
                        user.Disconnect();
                        this.OnPart(user);
                        Stats.UsersParted++;
                        Stats.KickedUsers++;
                    }

                    break;

                case 70: // request supernodes
                    byte[] s_nodes = UserPool.GetSuperNodes();
                    if (s_nodes == null) break;
                    user.SendOutboundPacket(ServerOutboundPackets.SuperNodesPacket(s_nodes));
                    break;

                case 74: // command text
                    if (!user.has_captched)
                        return;

                    text = packet.ReadString();
                    text = text.Replace("", "");
                    cmd = CommandEval.EvaluateClientCommandString(text);
                    this.OnCommand(user, cmd.cmdText, cmd.tUser, cmd.args);
                    break;

                case 82: // command login
                    text = packet.ReadString();
                    this.OnCommand(user, "login " + text, null, String.Empty);
                    break;

                case 112: // we are server - client requests link login
                    if (this.link_client.IsSet() || !CoreSettings.allow_linking) // already linked
                    {
                        user.SendOutboundPacket(ServerOutboundPackets.LinkErrorPacket((byte)LinkClient.LinkError.LinkingDisabled));
                        user.Disconnect();
                        return;
                    }

                    this.link_client = new LinkClient(user.socket, this.time_now);
                    user.UserWasLinkLeaf();
                    packet.SkipBytes(2);
                    temp_int16 = packet.ReadInt16();
                    ushort _tempint162 = packet.ReadInt16();
                    this.link_client.target_port = _tempint162;
                    this.link_client.room_name = packet.ReadString();
                    user = new UserObject();
                    user.physical_ip = this.link_client.target_ip;

                    if (!CoreSettings.link_any)
                    {
                        Ares.CommonObjects.ChannelListItem _c = new Ares.CommonObjects.ChannelListItem();
                        _c.port = this.link_client.target_port;
                        _c.externalIp = this.link_client.target_ip;
                        _c.name = this.link_client.room_name;

                        if (!LinkAllowList.CanAcceptLink(_c)) // not on allow list
                        {
                            this.link_client.SendOutboundPacket(ServerOutboundPackets.LinkErrorPacket((byte)LinkClient.LinkError.NotOnAllowList));
                            this.link_client.SendAndDisconnect();
                            return;
                        }
                    }

                    if (Bans.IsLinkBanned(user) || RangeBans.IsBanned(user)) // banned user
                    {
                        this.link_client.SendOutboundPacket(ServerOutboundPackets.LinkErrorPacket((byte)LinkClient.LinkError.BannedUser));
                        this.link_client.SendAndDisconnect();
                        return;
                    }

                    UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: " + this.link_client.room_name + " requesting room link..."));

                    if (temp_int16 < 3041) // proto out of date
                    {
                        UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: rejected [invalid link protocol]"));
                        this.link_client.SendOutboundPacket(ServerOutboundPackets.LinkErrorPacket((byte)LinkClient.LinkError.OldProtocol));
                        this.link_client.SendAndDisconnect();
                        return;
                    }

                    if (AutoLogin.IsHost(this.link_client.target_ip) && _tempint162 == CoreSettings.room_port) // linking yourself
                    {
                        UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: rejected [self linking]"));
                        this.link_client.SendOutboundPacket(ServerOutboundPackets.LinkErrorPacket((byte)LinkClient.LinkError.LinkToSelf));
                        this.link_client.SendAndDisconnect();
                        return;
                    }

                    UserPool.Broadcast(ServerOutboundPackets.AnnoucePacket("\x000314room link: accepted link request from " + this.link_client.room_name + " (Y)"));
                    this.link_client.SendOutboundPacket(ServerOutboundPackets.RequestLinkAckPacket());

                    foreach (UserObject usr in UserPool.Users)
                        if (usr.IsSet())
                            if (usr.logged_in)
                                if (usr.vroom == 0)
                                    this.link_client.SendOutboundPacket(ServerOutboundPackets.UserListItemPacket(usr, true));

                    this.link_client.SendOutboundPacket(ServerOutboundPackets.UserListEndPacket());
                    this.link_client.logged_in = true;
                    this.link_client.SetRoomInfo();

                    foreach (UserObject usr in UserPool.Users)
                    {
                        if (usr.IsSet())
                        {
                            if (usr.logged_in)
                            {
                                if (usr.vroom == 0)
                                {
                                    this.link_client.SendOutboundPacket(ServerOutboundPackets.AvatarPacket(usr));
                                    this.link_client.SendOutboundPacket(ServerOutboundPackets.PersonalMessagePacket(usr, usr.user_message));
                                }
                            }
                        }
                    }

                    AdminLog.WriteLine(this.link_client.room_name + " has requested and been accepted to link");
                    break;

                case 200: // custom data
                    text = packet.ReadString();
                    name = packet.ReadString();
                    UserObject tUser = UserPool.GetUserByName(name);

                    if (tUser != null)
                    {
                        tUser.SendOutboundPacket(ServerOutboundPackets.CustomDataPacket(user, text, packet.ReadBytes()));
                    }

                    break;

                case 201: // custom data to all
                    text = packet.ReadString();
                    UserPool.BroadcastCustomData(user, ServerOutboundPackets.CustomDataPacket(user, text, packet.ReadBytes()));
                    break;

                case 250:
                    ushort adf_len = packet.ReadInt16();
                    byte adf_id = packet.ReadByte();
                    byte[] adf_data = packet.ReadBytes();

                    if (adf_data.Length == adf_len)
                        this.ProcessAdvancedFeaturesPacket(user, adf_id, new AresDataPacket(adf_data));

                    break;
            }
        }

        private void ProcessAdvancedFeaturesPacket(UserObject user, byte id, AresDataPacket packet)
        {
            switch (id)
            {
                case 202: // add tag
                    this.ProcessCustomDataAddTags(user, packet);
                    break;

                case 203: // remove tag
                    this.ProcessCustomDataRemTags(user, packet);
                    break;

                case 204: // font
                    if (packet.Remaining() == 2)
                    {
                        user.font = new UserFont("Verdana", 10);
                        UserPool.Broadcast(user.vroom, ServerOutboundPackets.Font(user));
                        user.font = null;
                    }
                    else
                    {
                        int _f_size = (int)packet.ReadByte();
                        String _f_name = packet.ReadString();
                        user.font = new UserFont(_f_name, _f_size);

                        if (packet.Remaining() >= 2)
                        {
                            user.font.ncol = packet.ReadByte();
                            user.font.tcol = packet.ReadByte();
                        }

                        if (packet.Remaining() == 6)
                        {
                            user.font.name_ex = packet.ReadBytes(3);
                            user.font.text_ex = packet.ReadBytes(3);
                        }

                        UserPool.Broadcast(user.vroom, ServerOutboundPackets.Font(user));
                    }
                    break;

                case 205:
                    user.voice_chat = packet.ReadByte() == 1;
                    user.voice_private_chat = packet.ReadByte() == 1;
                    UserPool.Broadcast(user.vroom, ServerOutboundPackets.VoiceChatUserSupport(user));
                    break;

                case 206:
                    this.ProcessVCFirst(user, packet);
                    break;

                case 207:
                    this.ProcessVCFirstTo(user, packet);
                    break;

                case 208:
                    this.ProcessVCChunk(user, packet);
                    break;

                case 209:
                    this.ProcessVCChunkTo(user, packet);
                    break;

                case 210:
                    this.ProcessVCIgnore(user, packet);
                    break;

                case 220: // supports custom emotes
                    this.ProcessSupportsCustomEmotes(user);
                    break;

                case 221: // custom emote item
                    this.ProcessCustomEmoteUpload(user, packet);
                    break;

                case 222: // delete custom emote
                    this.ProcessCustomEmoteDelete(user, packet);
                    break;
            }
        }

        private void ProcessSupportsCustomEmotes(UserObject userobj)
        {
            if (!CoreSettings.supports_custom_emoticons)
                return;

            if (!userobj.supports_custom_emotes)
            {
                userobj.supports_custom_emotes = true;
                userobj.custom_emotes.Clear();
                UserPool.SendCustomEmotes(userobj);
            }
        }

        private void ProcessCustomEmoteUpload(UserObject userobj, AresDataPacket packet)
        {
            if (!CoreSettings.supports_custom_emoticons)
                return;

            ProcessSupportsCustomEmotes(userobj);

            CustomEmoteItem c = new CustomEmoteItem();
            c.Shortcut = packet.ReadString();
            c.Size = packet.ReadByte();
            c.Image = packet.ReadBytes();
            UserPool.BroadcastToVroomCustomEmotes(userobj.vroom, ServerOutboundPackets.CustomEmoteItem(userobj, c));
            userobj.custom_emotes.Add(c);

            if (userobj.custom_emotes.Count > 16)
            {
                userobj.Disconnect();
                this.OnPart(userobj);
            }
        }

        private void ProcessCustomEmoteDelete(UserObject userobj, AresDataPacket packet)
        {
            if (!CoreSettings.supports_custom_emoticons)
                return;

            String text = packet.ReadString();
            userobj.custom_emotes.RemoveAll(delegate(CustomEmoteItem x) { return x.Shortcut == text; });
            UserPool.BroadcastToVroomCustomEmotes(userobj.vroom, ServerOutboundPackets.CustomEmoteDelete(userobj, text));
        }

        private void ProcessCustomDataAddTags(UserObject userobj, AresDataPacket packet)
        {
            while (packet.Remaining() > 0)
                userobj.custom_tags.Add(packet.ReadString());
        }

        private void ProcessCustomDataRemTags(UserObject userobj, AresDataPacket packet)
        {
            while (packet.Remaining() > 0)
                userobj.custom_tags.RemoveAll(x => x == packet.ReadString());
        }

        private void ProcessVCFirst(UserObject userobj, AresDataPacket packet)
        {
            if (CoreSettings.can_vc)
            {
                byte[] buffer = ServerOutboundPackets.VoiceChatFirst(userobj.name, packet.ReadBytes());
                UserPool.BroadcastVoiceClip(userobj, buffer);
            }
        }

        private void ProcessVCFirstTo(UserObject userobj, AresDataPacket packet)
        {
            if (CoreSettings.can_vc)
            {
                String target_name = packet.ReadString();
                byte[] buffer = ServerOutboundPackets.VoiceChatFirstTo(userobj.name, packet.ReadBytes());
                UserObject target_obj = UserPool.GetUserByName(target_name);

                if (target_obj != null)
                    if (!target_obj.voice_chat_ignores.Contains(userobj.name))
                        if (target_obj.voice_private_chat)
                            target_obj.SendOutboundPacket(buffer);
                        else userobj.SendOutboundPacket(ServerOutboundPackets.VoiceChatNoPrivate(target_name));
                    else userobj.SendOutboundPacket(ServerOutboundPackets.VoiceChatIgnored(target_name));
                else userobj.SendOutboundPacket(ServerOutboundPackets.PMOfflinePacket(target_name));
            }
        }

        private void ProcessVCChunk(UserObject userobj, AresDataPacket packet)
        {
            if (CoreSettings.can_vc)
            {
                byte[] buffer = ServerOutboundPackets.VoiceChatChunk(userobj.name, packet.ReadBytes());
                UserPool.BroadcastVoiceClip(userobj, buffer);
            }
        }

        private void ProcessVCChunkTo(UserObject userobj, AresDataPacket packet)
        {
            if (CoreSettings.can_vc)
            {
                String target_name = packet.ReadString();
                byte[] buffer = ServerOutboundPackets.VoiceChatChunkTo(userobj.name, packet.ReadBytes());
                UserObject target_obj = UserPool.GetUserByName(target_name);

                if (target_obj != null)
                    if (target_obj.voice_private_chat)
                        if (!target_obj.voice_chat_ignores.Contains(userobj.name))
                            target_obj.SendOutboundPacket(buffer);
            }
        }

        private void ProcessVCIgnore(UserObject userobj, AresDataPacket packet)
        {
            if (CoreSettings.can_vc)
            {
                String target_name = packet.ReadString();

                if (userobj.voice_chat_ignores.Contains(target_name))
                {
                    userobj.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket(target_name + " is voice chat unignored"));
                    userobj.voice_chat_ignores.RemoveAll(x => x == target_name);
                }
                else
                {
                    userobj.SendOutboundPacket(ServerOutboundPackets.AnnoucePacket(target_name + " is voice chat ignored"));
                    userobj.voice_chat_ignores.Add(target_name);
                }
            }
        }
    }
}
